﻿namespace Example.Cryptography
{
    using System;
    using System.Security;

    using Xunit;

    public class SensitiveDataFacts
    {
        [Fact]
        public void ctor()
        {
            Assert.NotNull(new SensitiveData());
        }

        [Fact]
        public void ctor_SecureString()
        {
            using (var secret = new SecureString())
            {
                Assert.NotNull(new SensitiveData(secret));
            }
        }

        [Fact]
        public void ctor_SecureStringNull()
        {
            Assert.NotNull(new SensitiveData(null as SecureString));
        }

        [Fact]
        public void ctor_string()
        {
            Assert.NotNull(new SensitiveData("test"));
        }

        [Fact]
        public void ctor_stringEmpty()
        {
            Assert.NotNull(new SensitiveData(string.Empty));
        }

        [Fact]
        public void ctor_stringNull()
        {
            Assert.NotNull(new SensitiveData(null as string));
        }

        [Fact]
        public void is_IComparable()
        {
            Assert.IsAssignableFrom<IComparable>(new SensitiveData());
        }

        [Fact]
        public void opEquality_SensitiveData_SensitiveData_False()
        {
            var operand1 = new SensitiveData("b");
            var operand2 = new SensitiveData("a");

            Assert.False(operand1 == operand2);
        }

        [Fact]
        public void opEquality_SensitiveData_SensitiveData_Same()
        {
            var operand1 = new SensitiveData();
            var operand2 = operand1;

            Assert.True(operand1 == operand2);
        }

        [Fact]
        public void opEquality_SensitiveData_SensitiveData_True()
        {
            var operand1 = new SensitiveData();
            var operand2 = new SensitiveData();

            Assert.True(operand1 == operand2);
        }

        [Fact]
        public void opGreater_SensitiveData_SensitiveData_False()
        {
            var expr1 = new SensitiveData("a");
            var expr2 = new SensitiveData("b");

            Assert.False(expr1 > expr2);
        }

        [Fact]
        public void opGreater_SensitiveData_SensitiveData_Same()
        {
            var expr1 = new SensitiveData();
            var expr2 = expr1;

            Assert.False(expr1 > expr2);
        }

        [Fact]
        public void opGreater_SensitiveData_SensitiveData_True()
        {
            var expr1 = new SensitiveData("b");
            var expr2 = new SensitiveData("a");

            Assert.True(expr1 > expr2);
        }

        [Fact]
        public void opInequality_SensitiveData_SensitiveData_False()
        {
            var operand1 = new SensitiveData();
            var operand2 = new SensitiveData();

            Assert.False(operand1 != operand2);
        }

        [Fact]
        public void opInequality_SensitiveData_SensitiveData_Same()
        {
            var operand1 = new SensitiveData();
            var operand2 = operand1;

            Assert.False(operand1 != operand2);
        }

        [Fact]
        public void opInequality_SensitiveData_SensitiveData_True()
        {
            var operand1 = new SensitiveData("b");
            var operand2 = new SensitiveData("a");

            Assert.True(operand1 != operand2);
        }

        [Fact]
        public void opLesser_SensitiveData_SensitiveData_False()
        {
            var expr1 = new SensitiveData("b");
            var expr2 = new SensitiveData("a");

            Assert.False(expr1 < expr2);
        }

        [Fact]
        public void opLesser_SensitiveData_SensitiveData_Same()
        {
            var expr1 = new SensitiveData();
            var expr2 = expr1;

            Assert.False(expr1 < expr2);
        }

        [Fact]
        public void opLesser_SensitiveData_SensitiveData_True()
        {
            var expr1 = new SensitiveData("a");
            var expr2 = new SensitiveData("b");

            Assert.True(expr1 < expr2);
        }

        [Fact]
        public void op_CompareTo_object()
        {
            var obj = new SensitiveData();

            Assert.Equal(0, new SensitiveData().CompareTo(obj));
        }

        [Fact]
        public void op_CompareTo_objectGreater()
        {
            var obj = new SensitiveData("a");

            Assert.True(new SensitiveData("b").CompareTo(obj) > 0);
        }

        [Fact]
        public void op_CompareTo_objectInt32()
        {
            Assert.Throws<ArgumentException>(() => new SensitiveData().CompareTo(123));
        }

        [Fact]
        public void op_CompareTo_objectLesser()
        {
            var obj = new SensitiveData("b");

            Assert.True(new SensitiveData("a").CompareTo(obj) < 0);
        }

        [Fact]
        public void op_CompareTo_objectNull()
        {
            Assert.True(new SensitiveData().CompareTo(null) > 0);
        }

        [Fact]
        public void op_Compare_SensitiveDataDefault_SensitiveDataDefault_equal()
        {
            const int expected = 0;
            var actual = SensitiveData.Compare(new SensitiveData(), new SensitiveData());

            Assert.Equal(expected, actual);
        }

        [Fact]
        public void op_Compare_SensitiveDataDefault_SensitiveData_equal()
        {
            const int expected = -1;
            var actual = SensitiveData.Compare(new SensitiveData(), new SensitiveData("test"));

            Assert.Equal(expected, actual);
        }

        [Fact]
        public void op_Compare_SensitiveData_SensitiveDataDefault_equal()
        {
            const int expected = 1;
            var actual = SensitiveData.Compare(new SensitiveData("test"), new SensitiveData());

            Assert.Equal(expected, actual);
        }

        [Fact]
        public void op_Compare_SensitiveData_SensitiveData_equal()
        {
            const int expected = 0;
            var actual = SensitiveData.Compare(new SensitiveData("abc"), new SensitiveData("abc"));

            Assert.Equal(expected, actual);
        }

        [Fact]
        public void op_Compare_SensitiveData_SensitiveData_greater()
        {
            const int expected = 1;
            var actual = SensitiveData.Compare(new SensitiveData("b"), new SensitiveData("a"));

            Assert.Equal(expected, actual);
        }

        [Fact]
        public void op_Compare_SensitiveData_SensitiveData_lesser()
        {
            const int expected = -1;
            var actual = SensitiveData.Compare(new SensitiveData("a"), new SensitiveData("b"));

            Assert.Equal(expected, actual);
        }

        [Fact]
        public void op_Compare_SensitiveData_SensitiveData_longer()
        {
            const int expected = 1;
            var actual = SensitiveData.Compare(new SensitiveData("ab"), new SensitiveData("a"));

            Assert.Equal(expected, actual);
        }

        [Fact]
        public void op_Compare_SensitiveData_SensitiveData_shorter()
        {
            const int expected = -1;
            var actual = SensitiveData.Compare(new SensitiveData("a"), new SensitiveData("ab"));

            Assert.Equal(expected, actual);
        }

        [Fact]
        public void op_Equals_object()
        {
            var obj = new SensitiveData("test");

            Assert.True(new SensitiveData("test").Equals(obj));
        }

        [Fact]
        public void op_Equals_objectNull()
        {
            Assert.False(new SensitiveData().Equals(null));
        }

        [Fact]
        public void op_Equals_objectString()
        {
            Assert.False(new SensitiveData("example").Equals("example"));
        }

        [Fact]
        public void op_Equals_object_this()
        {
            var obj = new SensitiveData();

            Assert.True(obj.Equals(obj));
        }

        [Fact]
        public void op_GetHashCode()
        {
            Assert.True(new SensitiveData("test").GetHashCode() > 0);
        }

        [Fact]
        public void op_GetHashCode_default()
        {
            const int expected = 0;
            var actual = new SensitiveData().GetHashCode();

            Assert.Equal(expected, actual);
        }

        [Fact]
        public void op_ToInsecureBytes()
        {
            var expected = new[]
                               {
                                   Convert.ToByte('a'), Convert.ToByte('b'), Convert.ToByte('c')
                               };

            using (var secret = new SecureString())
            {
                secret.AppendChar('a');
                secret.AppendChar('b');
                secret.AppendChar('c');

                var actual = new SensitiveData(secret).ToInsecureBytes();

                Assert.Equal(expected.Length, actual.Length);
                for (var i = 0; i < expected.Length; i++)
                {
                    Assert.Equal(expected[i], actual[i]);
                }
            }
        }

        [Fact]
        public void op_ToInsecureBytes_empty()
        {
            Assert.Equal(0, new SensitiveData(string.Empty).ToInsecureBytes().Length);
        }

        [Fact]
        public void op_ToInsecureBytes_null()
        {
            Assert.Null(new SensitiveData().ToInsecureBytes());
        }

        [Fact]
        public void op_ToInsecureChars()
        {
            var expected = new[]
                               {
                                   'a', 'b', 'c'
                               };

            using (var secret = new SecureString())
            {
                secret.AppendChar('a');
                secret.AppendChar('b');
                secret.AppendChar('c');

                var actual = new SensitiveData(secret).ToInsecureChars();

                Assert.Equal(expected.Length, actual.Length);
                for (var i = 0; i < expected.Length; i++)
                {
                    Assert.Equal(expected[i], actual[i]);
                }
            }
        }

        [Fact]
        public void op_ToInsecureChars_empty()
        {
            Assert.Equal(0, new SensitiveData(string.Empty).ToInsecureChars().Length);
        }

        [Fact]
        public void op_ToInsecureChars_null()
        {
            Assert.Null(new SensitiveData().ToInsecureChars());
        }

        [Fact]
        public void op_ToInsecureString()
        {
            const string expected = "abc";

            using (var secret = new SecureString())
            {
                secret.AppendChar('a');
                secret.AppendChar('b');
                secret.AppendChar('c');

                var actual = new SensitiveData(secret).ToInsecureString();

                Assert.Equal(expected, actual);
            }
        }

        [Fact]
        public void op_ToInsecureString_empty()
        {
            Assert.Equal(string.Empty, new SensitiveData(string.Empty).ToInsecureString());
        }

        [Fact]
        public void op_ToInsecureString_null()
        {
            Assert.Null(new SensitiveData().ToInsecureChars());
        }

        [Fact]
        public void op_ToSecureString()
        {
            using (var expected = new SecureString())
            {
                expected.AppendChar('a');
                expected.AppendChar('b');
                expected.AppendChar('c');

                var actual = new SensitiveData(expected).ToSecureString();

                Assert.Equal(expected, actual);
            }
        }

        [Fact]
        public void op_ToString()
        {
            var expected = string.Empty;
            var actual = new SensitiveData("test").ToString();

            Assert.Equal(expected, actual);
        }
    }
}